//Header files
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <libspe2.h>
#include <cbe_mfc.h>
#include <pthread.h>
#include <unistd.h>
#include <math.h>
#include <time.h>
#include "common.h"

//Decalring matrices
unsigned int matrixA[MATRIX_SIZE][MATRIX_SIZE]  QWORD_ALIGNED;
unsigned int matrixB[MATRIX_SIZE][MATRIX_SIZE]  QWORD_ALIGNED;
unsigned int matrixC[MATRIX_SIZE][MATRIX_SIZE]  QWORD_ALIGNED;
unsigned int matrixA2[THREAD_NUM][NUM+1][MATRIX_SIZE] QWORD_ALIGNED;
unsigned int matrixB2[THREAD_NUM][MATRIX_SIZE][NUM+1] QWORD_ALIGNED;
unsigned int matrixC2[ITERATIONS][ITERATIONS][NUM+1][NUM+1] QWORD_ALIGNED;
		
//============================================================================
CONTROL_BLOCK cb[THREAD_NUM] QWORD_ALIGNED;
extern spe_program_handle_t spu;
//============================================================================

// Data structure for running SPE thread
typedef struct ppu_pthread_data {
	spe_context_ptr_t speid;
	pthread_t pthread;
	void *argp;
	int tid QWORD_ALIGNED;
} ppu_pthread_data_t;

//Declaring struct
ppu_pthread_data_t data[THREAD_NUM];

// create and run one SPE thread
void *ppu_pthread_function(void *arg) {
	ppu_pthread_data_t *datap = (ppu_pthread_data_t *)arg;
	int rc;
 	unsigned int entry = SPE_DEFAULT_ENTRY;

	if ((rc = spe_context_run(datap->speid, &entry, 0, datap->argp,(void *) datap->tid, NULL)) < 0) {
		fprintf (stderr, "Failed spe_context_run(rc=%d, errno=%d, strerror=%s)\n", rc, errno, strerror(errno));
		exit (1);
	}

  pthread_exit(NULL);
}

int main() {
  
	//Declarations
	int rc;
	int thread_count = 0;
	int i,j,k,m,n;
 	srand( time(NULL) );    //initialize timer

	// Initialize matrix elements
  	for(i=0; i<MATRIX_SIZE; i++)
	{
		for(j=0; j<MATRIX_SIZE; j++)
		{
			matrixA[i][j] = 0;
			matrixB[i][j] = 0;
			matrixA[i][j] = (int) rand()%10;
			matrixB[i][j] = (int) rand()%10;
		}
	}

	for(i=0;i<THREAD_NUM;i++)
	{
		// give each thread an ID
		data[i].tid = i;
		
		// create SPE context and load SPE program into the SPE context
		if ((data[i].speid = spe_context_create (0, NULL)) == NULL)
		{
			fprintf (stderr, "Failed spe_context_create(errno=%d strerror=%s)\n", errno, strerror(errno));
			exit (3);
		}
  
		//Load program
		if ((rc = spe_program_load (data[i].speid, &spu)) != 0)
		{
			fprintf (stderr, "Failed spe_program_load(errno=%d strerror=%s)\n", errno, strerror(errno));
			exit (3);
		}
	}


	#ifdef TIMING  
		long unsigned int start_t, end_t;
		start_t=__mftb();
	#endif

  
  	//This part is used to Data matrixInPPE for spu.c in only one SPE  
	for(m=0;m<ITERATIONS;m++)
	{
		thread_count = 0;
		for(i=0;i<ITERATIONS;i++)
		{
			//Putting proper data from A2 into A2
			for(j=0;j<NUM;j++)
			{
				for(k=0;k<MATRIX_SIZE;k++)
				{
					matrixA2[thread_count%6][j][k] = matrixA[j+m*NUM][k];
				}
			}
			//Putting proper data from B into B2
			for(j=0;j<MATRIX_SIZE;j++)
			{
				for(k=0;k<NUM;k++)
				{
					matrixB2[thread_count%6][j][k] = matrixB[j][k+i*NUM];
				}
			}
			
	  		// Fill in control block with matrix data
	  		cb[thread_count%6].a = (unsigned int)&matrixA2[thread_count%6][0][0];
	  		cb[thread_count%6].b = (unsigned int)&matrixB2[thread_count%6][0][0];
	  		cb[thread_count%6].c = (unsigned int)&matrixC2[m][i][0][0];
	
			data[thread_count%6].argp = (unsigned long long *) &cb[thread_count%6];

			// create SPE pthreads
			if ((rc = pthread_create (&data[thread_count%6].pthread, NULL, &ppu_pthread_function, &data[thread_count%6])) != 0)
			{
				fprintf (stderr, "Failed pthread_create(errno=%d strerror=%s)\n", errno, strerror(errno));
				exit (3);
			}      
	  		
			// wait for SPE to complete
			if ((rc = pthread_join (data[thread_count%6].pthread, NULL)) != 0)
			{
				fprintf (stderr, "Failed pthread_join(rc=%d, errno=%d strerror=%s)\n", rc, errno, strerror(errno));
				exit (1);
			}
			thread_count++;
		}	
	}
	
	for(i=0;i<THREAD_NUM;i++)
	{
		// destroy the SPE contexts	
		if ((rc = spe_context_destroy (data[i].speid)) != 0)
		{
			fprintf (stderr, "Failed spe_context_destroy(rc=%d, errno=%d strerror=%s)\n", rc, errno, strerror(errno));
			exit (1);
		}
	}

	//Loading matrix info back into proper format
	for(i=0;i<ITERATIONS;i++)
	{
		for(j=0;j<ITERATIONS;j++)
		{
			for(k=0;k<NUM;k++)
			{
				for(m=0;m<NUM;m++)
				{
					//printf("%d ", matrixC2[i][j][k][m]);
					matrixC[i*NUM+k][j*NUM+m] = matrixC2[i][j][k][m];
				}
			}
		}
	}

	//Printing out result
	for(i=0;i<MATRIX_SIZE;i++)
	{
		for(j=0;j<MATRIX_SIZE;j++)
		{
			printf("%d ", matrixC[i][j]);
		}
		printf("\n");
	}

	//End timing
	#ifdef TIMING  
		//compute the timein us(10E-6)       
		end_t=__mftb();
	#endif

	//Display timing
	printf("\nTotal time used by SPE: %f us\n", (float)((end_t-start_t)/79.8)); 
  
  return 0;
}
